์›ํ‹ฐ๋“œ ํ”„๋ฆฌ์˜จ๋ณด๋”ฉ 2-2 ๊ณผ์ œํšŒ๊ณ 

@choi2021 ยท November 07, 2022 ยท 15 min read

๐Ÿ“œ ๊ณผ์ œ ์„ค๋ช…

์ด๋ฒˆ ๊ณผ์ œ๋Š” ์ด๋ฒˆ ๊ธฐ๊ฐ„์— ์ฐธ๊ฐ€ํ•œ ๊ธฐ์—…์˜ ๊ณผ์ œ๋Š” ์•„๋‹ˆ์ง€๋งŒ ์ด์ „ ๊ธฐ์ˆ˜์—์„œ ์ฃผ์–ด์กŒ๋˜ ๊ณผ์ œ๋กœ, ๋ฉ˜ํ† ๋‹˜๊ป˜์„œ ์ข‹์€ ๊ณผ์ œ๋ผ ์ƒ๊ฐํ•˜์…”์„œ ๋„ฃ์œผ์…จ๋‹ค๊ณ  ํ–ˆ๋‹ค. ๊ธฐ์—…์€ ๊ด‘๊ณ  ํšŒ์‚ฌ๋กœ ๊ด‘๊ณ ์™€ ๋งˆ์ผ€ํŒ…๊ณผ ๊ด€๋ จ๋œ ๋ฐ์ดํ„ฐ๋“ค์„ ๋ณด์—ฌ ์ฃผ๋Š” ๋Œ€์‹œ๋ณด๋“œ์™€ ๊ด€๋ฆฌ ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“œ๋Š” ๊ณผ์ œ์˜€๋‹ค. ๋‚œ์ƒ ์ฒ˜์Œ ๋ณด๋Š” ์šฉ์–ด๋“ค๊ณผ ๋„ˆ๋ฌด ๋””ํ…Œ์ผ ํ•˜๊ฒŒ ๊ตฌ์„ฑ๋˜์–ด์žˆ๋Š” ํ”ผ๊ทธ๋งˆ ํŽ˜์ด์ง€ ๋•Œ๋ฌธ์— ์—ฌํƒœ ๊นŒ์ง€ ๊ณผ์ œ๋“ค ์ค‘์—์„œ ๊ฐ€์žฅ ํž˜๋“  ๊ณผ์ œ๊ฐ€ ๋˜์—ˆ๋‹ค...

์ €๋ฒˆ๊ณผ ๋™์ผํ•˜๊ฒŒ Typescript์™€ Context API, useReducer๋ฅผ ํ™œ์šฉํ•ด ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ํ–ˆ๋‹ค. ๊ทธ๋ž˜ํ”„๋ฅผ ์œ„ํ•ด์„œ๋Š” APEXCHART ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, ๋‚ ์งœ ๋ณ€๊ฒฝ์„ ์œ„ํ•ด์„œ๋Š” reat-picker ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•ด ์ง„ํ–‰ํ–ˆ๋‹ค. ์ด๋ฒˆ์— ๊ณผ์ œ๋ฅผ ํ•˜๋ฉด์„œ ์‹œ๋„ํ•ด๋ณด๊ณ  ์‹ถ์—ˆ๋˜ ๊ฒƒ์€ ๋‘ ๊ฐ€์ง€๋กœ, ์ˆ˜์—…์„ ํ†ตํ•ด ๋ฐฐ์› ๋˜ ๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ์™€ redux๋ฅผ ์ด์šฉํ•œ ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ์˜€๋‹ค. ํ•˜์ง€๋งŒ ๊ธฐ๋Šฅ ๊ตฌํ˜„์—๋„ ๋„ˆ๋ฌด ๋งŽ์€ ์‹œ๊ฐ„์ด ๋“ค์–ด, redux๋ฅผ ์‹œ๋„ํ•  ์‹œ๊ฐ„์ด ์—†์ด ๋น ๋ฅด๊ฒŒ ContextAPI๋ฅผ ์ด์šฉํ•ด ์ „์—ญ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ–ˆ๋‹ค.

๐ŸŽˆ ๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ

๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ๋Š” ์ข‹์€ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ์ค€์ด ๋˜์–ด ์ค€๋‹ค. KISS (Keep it simple stupid)๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” ์›์น™์€ ํ•˜๋‚˜์˜ ๋ชจ๋“ˆ์ด ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ๋งŒ์„ ํ•˜๊ฒŒํ•˜๋ผ๋Š” ๋œป์ด๋‹ค. ์ฒ˜์Œ์— ์ƒ๊ฐํ–ˆ์„ ๋•Œ ํ•˜๋‚˜์˜ ๋ชจ๋“ˆ์ด ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๊ธฐ๋Šฅ์„ ํ•˜๊ฒŒ ๋˜๋ฉด ๋” ์ข‹์ง€ ์•Š์„๊นŒ ๋ผ๋Š” ์ƒ๊ฐ์„ ํ–ˆ์ง€๋งŒ, ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ์ด ์—‰์ผœ ์žˆ๋‹ค๋ฉด ํ•ด๋‹น ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋‹ค ๋‹ค๋ฅธ ๊ธฐ๋Šฅ์ด ์˜ํ–ฅ์„ ๋ฐ›๋Š” ์ผ์ด ์ƒ๊ธฐ๊ฒŒ ๋œ๋‹ค. ์ด๋Ÿฌํ•œ ์ƒํƒœ๋ฅผ ํ”ํžˆ "์ŠคํŒŒ๊ฒŒํ‹ฐ ์ฝ”๋“œ"๋ผ๊ณ  ๋ถˆ๋ฆฐ๋‹ค.

์‚ฌ์‹ค ํ•จ์ˆ˜๋ฅผ ๋ฐฐ์šฐ๋ฉด์„œ ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ๋งŒ ํ•˜๊ฒŒ ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์›์น™์€ ๋„ˆ๋ฌด ๋งŽ์ด ๋“ค์–ด์™”์ง€๋งŒ, ๋‚˜์ค‘์— ๋‚ด๊ฐ€ ํ–ˆ๋˜ ํ”„๋กœ์ ํŠธ์˜ ์ฝ”๋“œ๋ฅผ ๋Œ์•„๋ณด๋ฉด ๋„์ €ํžˆ ์†๋Œˆ ์ˆ˜ ์—†์ด ์—‰์ผœ์žˆ๋Š” ๊ฒƒ์„ ๋ณด๊ฒŒ ๋œ๋‹ค. ์ด๋Ÿฌํ•œ ํ•˜๋‚˜์˜ ๋ชจ๋“ˆ์ด ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ๋งŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ๊ด€์‹ฌ์‚ฌ๋ฅผ ํ•˜๋‚˜์— ๋‘์ง€ ์•Š๋Š” ๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ๋‚ด๊ฐ€ ์ข‹์•„ํ•˜๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋Š” ๋ฉ€ํ‹ฐ ํŒจ๋Ÿฌ๋‹ค์ž„ ์–ธ์–ด์ด๊ธฐ ๋•Œ๋ฌธ์—, ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ๊ณผ ๊ฐ์ฒด์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋‘ ๊ฐ€์ง€ ๋ชจ๋‘ ๊ฐ€๋Šฅํ•ด, class๋กœ ๋ชจ๋“ˆ์„ ๋‚˜๋ˆ„๊ณ , ํ•„์š”ํ•œ ๊ณณ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ์ „๋‹ฌ (Dependency Injection)ํ•˜๋ฉด ์‚ฌ์šฉํ•˜๋Š” ๊ณณ์—์„œ๋Š” ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑ๋˜์–ด์žˆ๋Š”์ง€ ์ž์„ธํžˆ ์•Œ์ง€ ๋ชปํ•ด๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ถ”์ƒํ™”๊ฐ€ ๋œ ๋ชจ๋“ˆ๋“ค๋กœ ์—ฐ๊ฒฐํ•ด ๋‚˜๊ฐˆ ์ˆ˜ ์žˆ๋‹ค.

์ด์ „์— class๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ์„ ํ•˜๋Š” ๊ด€๋ จ๋œ ๊ฒƒ๋“ค์„ ๋ชจ์•„๋‘์ž๋Š” ๊ธฐ์ค€์œผ๋กœ index.tsx์—์„œ instance๋ฅผ ๋งŒ๋“ค์–ด ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹์„ ์•Œ๊ณ ๋Š” ์žˆ์—ˆ์ง€๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ณณ๊นŒ์ง€ ๊ณ„์†ํ•ด์„œ prop์œผ๋กœ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹์„ ์ด์šฉํ–ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์€ ๋˜‘๊ฐ™์ด prop-drilling์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜๊ณ  ์˜คํžˆ๋ ค ๋” ๋ณต์žกํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ์ด๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒŒ ์ด์ „์— ๋ฐฐ์› ๋˜ context API์˜€๋‹ค.

๊ทธ๋ฆฌ๊ณ  Typescript๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ณด๋‹ค ๊ฐ•๋ ฅํ•œ ๊ฐ์ฒด์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ๊ธฐ๋Šฅ๋“ค์„ ๋‹ด๊ณ  ์žˆ๋‹ค. Typescript์˜ interface๋Š” ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“ค๊ณ ์ž ํ•˜๋Š” ๋ชจ๋“ˆ์˜ ๋ช…์„ธ์„œ๋กœ ์–ด๋– ํ•œ ์ƒํƒœ์™€ ํ–‰๋™๋“ค์„ ํ•˜๋Š” ๋ชจ๋“ˆ์ธ์ง€ ์ •ํ•˜๊ณ , ๊ทธ๊ฒƒ์„ implementsํ•˜๋Š” class๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“– Interface

์ด๋ฒˆ ๊ณผ์ œ์—์„œ ๋งŒ๋“ค์—ˆ๋˜ class๋Š” ๋‘๊ฐ€์ง€ ๊ด‘๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ api๋กœ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ชจ๋“ˆ์ด ํ•„์š”ํ–ˆ๋‹ค. ๊ทธ๋ž˜์„œ interface๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‘ ๊ฐ€์ง€ ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

type GetAdListResponse = {
  ads: AdType[]
  counts: number
}

type GetTrendResponse = {
  report: {
    daily: TrendType[]
  }
}

interface AdService {
  getAdList: () => Promise<GetAdListResponse>
  getTrend: () => Promise<GetTrendResponse>
}

API ํ†ต์‹ ์„ ์œ„ํ•œ class์ด๋ฏ€๋กœ ๊ฐ ๋ฉ”์†Œ๋“œ๋Š” ํ†ต์‹ ๊ฒฐ๊ณผ์ธ promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  promise ๋‚ด๋ถ€์˜ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ์ •ํ•ด์ฃผ๋ฉด interface๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ“ Class

์šฐ๋ฆฌ๊ฐ€ ์ •์˜ํ•œ interface๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” class์ธ AdserviceImpl๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

import { AxiosError, AxiosInstance } from "axios"
import {
  AdService,
  GetAdListResponse,
  GetTrendResponse,
} from "models/interface"
import HTTPError from "../network/httpError"

const AD_LIST_URL = "/ad-list-data-set.json"
const AD_TREND_URL = "/trend-data-set.json"

class AdServiceImpl implements AdService {
  constructor(private axiosInstance: AxiosInstance) {}

  getAdList = async () => {
    try {
      const { data } = await this.axiosInstance.get<GetAdListResponse>(
        AD_LIST_URL
      )
      return data
    } catch (error) {
      const { response } = error as unknown as AxiosError
      if (response) {
        throw new HTTPError(response?.status, response?.statusText)
      }
      throw new Error("Unknown Error")
    }
  }

  getTrend = async () => {
    try {
      const { data } = await this.axiosInstance.get<GetTrendResponse>(
        AD_TREND_URL
      )
      return data
    } catch (error) {
      const { response } = error as unknown as AxiosError
      if (response) {
        throw new HTTPError(response?.status, response?.statusText)
      }
      throw new Error("Unknown Error")
    }
  }
}

export default AdServiceImpl

์ด์ „๋ถ€ํ„ฐ ํ•ญ์ƒ ์‚ฌ์šฉํ•ด์˜ค๋˜ HTTPError class๋Š” ์›ํ•˜๋Š” ์—๋Ÿฌ๋ฉ”์‹œ์ง€๋ฅผ ์ปค์Šคํ…€ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ์ •๋„๋กœ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ ์—๋Ÿฌ๊ฐ€ ๋‹ค์–‘ํ•ด์ง€๊ณ  ๋ณต์žกํ•ด์ง€๋ฉด class์— ๋‚ด์šฉ๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋˜๋‹ˆ๊นŒ ํ›จ์”ฌ ์œ ์ง€ ๋ณด์ˆ˜์— ์œ ์šฉํ•  ๊ฒƒ์ด๋ž€ ์˜ˆ์ƒ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

export default class HTTPError extends Error {
  constructor(private statusCode: number, public message: string) {
    super(message)
  }

  get errorMessage() {
    switch (this.statusCode) {
      case 404:
        this.message = "์ž˜๋ชป๋œ ์š”์ฒญ์ž…๋‹ˆ๋‹ค. url์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”"
        break
      default:
        throw new Error("Unknown Error")
    }
    return this.message
  }
}

๐Ÿ’Š Dependency Injection

์œ„์˜ ๋งŒ๋“  AdService๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” instance๋ฅผ ๋งŒ๋“ค๊ณ  instance๋ฅผ context API๋กœ ์ „๋‹ฌํ•ด์ฃผ๋ฉด ํ•„์š”ํ•œ ๊ณณ์—์„œ ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. typescript๋กœ contextAPI๋กœ instance๋ฅผ ์ „๋‹ฌํ•  ๋•Œ ์–ด๋–ป๊ฒŒ type์„ ์ „๋‹ฌํ•ด์•ผ ํ•  ์ง€ ํ•ด๊ฒฐํ•˜๋Š” ์™€์ค‘์—, ์šฐ๋ฆฌ๊ฐ€ ์ •์˜ํ•œ interface๋ฅผ ๊ทธ๋Œ€๋กœ ์ „๋‹ฌํ•ด์ฃผ๋ฉด type์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค. interface๋ฅผ ๊ธฐ์ค€์œผ๋กœ class๋ฅผ ๋งŒ๋“œ๋Š” ์˜์กด์„ฑ ์—ญ์ „ ์›์น™์„ ์ดํ•ดํ•˜๋Š” ๊ฒฝํ—˜์ด์—ˆ๋‹ค.

[adService์˜ type์„ interface๋กœ ์ „๋‹ฌํ•ด์ฃผ๊ธฐ ์ „์˜ ์—๋Ÿฌ]

์—ฌ๊ธฐ์„œ ์กฐ๊ธˆ ์ฃผ์˜ํ•  ๋ถ€๋ถ„์€ ์ „๋‹ฌํ•˜๋ฉด์„œ instance๊ฐ€ this๋ฅผ ์žƒ์–ด๋ฒ„๋ฆฐ๋‹ค๋Š” ์ ์ด๋‹ค. ๊ทธ๋ž˜์„œ bind๋กœ adService๋ฅผ binding์„ ํ•ด์ฃผ๊ฑฐ๋‚˜, method ์ž์ฒด๋ฅผ arrow function์œผ๋กœ ๋ฐ”๊พธ๊ฒŒ ๋˜๋ฉด, this binding ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

//index.tsx
const BASE_URL = process.env.REACT_APP_BASE_URL || ""
const axiosInstance = createAxiosClient(BASE_URL)
const adService = new AdService(axiosInstance)

const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)

root.render(
  <React.StrictMode>
    <AdServiceProvider adService={adService}>..</AdServiceProvider>
  </React.StrictMode>
)

//AdServiceContext.tsx
import { AdService } from "models/interface"
import { createContext, useMemo, useContext } from "react"

const AdServiceContext = createContext<AdService | null>(null)
export const useAds = () => useContext(AdServiceContext)

export const AdServiceProvider = ({
  children,
  adService,
}: {
  children: React.ReactNode
  adService: AdService //interface๋กœ ๋ฐ”๋กœ type์ •ํ•ด์ค„ ์ˆ˜ ์žˆ์–ด
}) => {
  const { getAdList, getTrend } = adService
  const value = useMemo(() => {
    return { getAdList, getTrend }
  }, [getAdList, getTrend])
  return (
    <AdServiceContext.Provider value={value}>
      {children}
    </AdServiceContext.Provider>
  )
}

๐Ÿ› React Query๋ฅผ ์ด์šฉํ•œ Refactoring

๊ธฐ๋Šฅ์„ ์šฐ์„ ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋‹ค ๋ณด๋‹ˆ ๋„ˆ๋ฌด ๋น„์Šทํ•œ ์ฝ”๋“œ๊ฐ€ ๋‘ ๋ฒˆ์ด๋‚˜ ์‚ฌ์šฉ๋˜์—ˆ๋Š”๋ฐ, ์ฝ”๋“œ ์–‘์ด ๋„ˆ๋ฌด ๋งŽ์•„ ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง€๋Š” ๋ฌธ์ œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์—ˆ๋‹ค.

const getAdList = useCallback(async () => {
    listDispatch({ type: DataActionEnum.SET_IS_LOADING, isLoading: true });
     try {
      const response = _await_ adService?.getAdList();
      listDispatch({
        type: DataActionEnum.SET_DATA,
        data: response?.ads || [],
      });
    } catch (e) {
      console.error(e);
    } finally {
      listDispatch({ type: DataActionEnum.SET_IS_LOADING, isLoading: false });
    }
  }, [adService, listDispatch]);

const getAdTrend = useCallback(async () => {
    trendDispatch({ type: DataActionEnum.SET_IS_LOADING, isLoading: true });
    try {
      const response = _await_ adService?.getTrend();
      trendDispatch({
        type: DataActionEnum.SET_DATA,
        data: response?.report.daily || [],
      });
    } catch (e) {
      console.error(e);
    } finally {
      trendDispatch({ type: DataActionEnum.SET_IS_LOADING, isLoading: false });
    }
  }, [adService, trendDispatch]);

dispatchํ•จ์ˆ˜์™€ action๋‚ด์šฉ๋งŒ ๋ฐ”๋€Œ๋Š” ๋‘ ๊ฐ€์ง€ ํ•จ์ˆ˜ ๋‚ด์šฉ์„ ๋ณด๋ฉด์„œ ํ•˜๋‚˜์˜ ํ•จ์ˆ˜๋กœ ๋งŒ๋“ค์–ด์„œ ์ „๋‹ฌํ•  ๊นŒ๋„ ์ƒ๊ฐํ–ˆ์ง€๋งŒ Type๊ณผ ๊ด€๋ จํ•ด์„œ ๋„ˆ๋ฌด ๋ณต์žกํ•ด์ง€๋Š” ๋ฌธ์ œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์—ˆ๋‹ค. ์ฝ”๋“œ์˜ ๊ฐ„๊ฒฐํ™”์™€ ๋‘ ๊ฐ€์ง€ ๋ถ„๋ฆฌ๋œ ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ง€๋Š” ๊ฒŒ ๋” ์ข‹์„ ๊ฒƒ ๊ฐ™์•„ React Query๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

React Query

React Query๋Š” ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๊ณ , ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๋ฅผ caching, retry ๋“ฑ ๋น„๋™๊ธฐ์— ํ•„์š”ํ•œ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด ์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‹ค. React Query๋Š” ์ดํ›„์— ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ์—ฐ๋™ํ•œ๋‹ค๋ฉด ์ƒํƒœ๋Š” ์ƒํƒœ๋ฅผ ๋ณด๊ด€ํ•˜๊ณ  ๋ณ€๊ฒฝํ•˜๋Š” ์—ญํ• ๋งŒ, React Query๋กœ๋Š” ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๋งŒ์„ ๊ด€๋ฆฌํ•˜๋Š” ์—ญํ• ๋งŒ ๋ถ„๋ฆฌํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์•„ ๊ณต๋ถ€ํ•˜๊ณ  ์ ์šฉํ•ด๋ณด์•˜๋‹ค.

์œ„ ์ƒํ™ฉ์€ ๋‘ ๊ฐ€์ง€ API ํ•จ์ˆ˜๋กœ ํ˜ธ์ถœ์„ ํ•˜๋Š” ์ƒํ™ฉ์ด๊ณ  ๊ฐ๊ฐ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒํƒœ๋กœ ์ „๋‹ฌํ•ด์ค˜์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด์—ˆ๋‹ค. (React query๊ฐ€ ๋‚˜์„œ๊ธฐ ๋”ฑ ์ข‹์€ ์ƒํ™ฉ์ด๋‹ค)

์ด๋ฅผ ๋ฐ›์•„์˜จ ๊ฒฐ๊ณผ๋“ค๋งŒ ๋ฝ‘์•„์„œ ์ƒํƒœ๋ฅผ ์ „๋‹ฌํ•˜๋‹ˆ, ํ›จ์”ฌ ์ฝ”๋“œ๊ฐ€ ๊ฐ„๋‹จํ•ด์ง€๊ณ  ๊ตฌ๋ถ„์ด ์ž˜๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

const { isLoading, data: trendData } = useQuery(
  ["trend"],
  () => adService?.getTrend(),
  {
    staleTime: 1000 * 60 * 60,
    cacheTime: 1000 * 60 * 60,
  }
)
const { data: listData } = useQuery(["adList"], () => adService?.getAdList(), {
  staleTime: 1000 * 60 * 60,
  cacheTime: 1000 * 60 * 60,
})

useEffect(() => {
  trendDispatch({
    type: DataActionEnum.SET_DATA,
    data: trendData?.report.daily || [],
  })
  trendDispatch({ type: DataActionEnum.SET_IS_LOADING, isLoading })
}, [trendData, isLoading])

useEffect(() => {
  listDispatch({
    type: DataActionEnum.SET_DATA,
    data: listData?.ads || [],
  })
}, [listData])

๐Ÿ˜…Typescript Error

์—ญ์‹œ Typescript๋Š” ์•„์ง ์–ด๋ ต๋‹ค... ๋งˆ์ฃผํ–ˆ๋˜ ์—๋Ÿฌ๋“ค์„ ๊ธฐ๋กํ•ด ์ดํ›„์— ๊นŒ๋จน์„ ๋‚ด๊ฐ€ ์ฐธ๊ณ ํ•˜๋ คํ•œ๋‹ค.

Object Key Type

์ €๋ฒˆ ๊ณผ์ œ์—์„œ Detail ํŽ˜์ด์ง€๋ฅผ ๊ตฌ์„ฑํ• ๋•Œ api๋กœ ๋ฐ›์€ ๋ฐ์ดํ„ฐ ์ค‘์—์„œ param๊ฐ’๊ณผ ๊ฐ™์€ id๋ฅผ ๊ฐ–๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์•„์„œ ๊ฐ€์ ธ์™”๋‹ค. ์ด๋ ‡๊ฒŒ ํ•  ๋•Œ ๋งŒ์•ฝ์— ๋ฐ์ดํ„ฐ๊ฐ€ ์ปค์ง„๋‹ค๋ฉด ์ฐพ๋Š”๋ฐ ํ›จ์”ฌ ์˜ค๋žœ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค (๋ฐฐ์—ด์—์„œ ์ฐพ๊ธฐ๋Š” O(n)). ๊ทธ๋ž˜์„œ Object๋กœ ์ž๋ฃŒ๊ตฌ์กฐ๋ฅผ ๋ฐ”๊พธ๋ ค ํ–ˆ์—ˆ๋Š”๋ฐ Type์„ ์–ด๋–ป๊ฒŒ ์„ค์ •ํ• ์ง€ ๋ชฐ๋ผ ํฌ๊ธฐํ–ˆ๋˜ ๊ฒฝํ—˜์ด ์žˆ๋‹ค.

์ด๋ฒˆ์—๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ณ„์‚ฐํ•˜๋ฉด์„œ Object.keys() ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ–ˆ๋Š”๋ฐ, ๋‚˜์—ดํ•˜๋ ค๋ฉด index์˜ type์„ ๊ฒฐ์ •ํ•ด์ค˜์•ผ ํ–ˆ๋‹ค.

export type ResultType = {
  [index: string]: number
}

const calculateData = (data: TrendType[]) => {
  const result: ResultType = {
    imp: 0,
    click: 0,
    cost: 0,
    conv: 0,
    convValue: 0,
    ctr: 0,
    cvr: 0,
    cpc: 0,
    cpa: 0,
    roas: 0,
  }

  data.forEach(item => {
    Object.keys(item).forEach(key => {
      if (key in result) {
        result[key] += Number(item[key])
      }
    })
  })
  return result
}

export default calculateData

๋งŒ์•ฝ ์ €๋ฒˆ ๊ณผ์ œ์—์„œ ํ•ด๊ฒฐ ๋ชปํ–ˆ๋˜ Object์˜ key๋กœ ๋ฐ์ดํ„ฐ์˜ id๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ value๋กœ ํ•œ ์ž๋ฃŒ๊ตฌ์กฐ์˜ type ๊ฒฝ์šฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

type Type = {
  [index: string]: { data: string }
}

const obj: Type = {
  "123123": { data: "hi" },
}

๐Ÿ˜ฅ์•„์‰ฌ์› ๋˜ ์ 

์ด๋ฒˆ ๊ณผ์ œ๋Š” ๋„ˆ๋ฌด ๋””ํ…Œ์ผํ•œ UI๋“ค๊ณผ selector๋ฅผ ์ด์šฉํ•ด ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋“ค์„ ํ•„ํ„ฐ ํ•ด์•ผ ํ•˜๋Š” ๋ถ€๋ถ„๋“ค์ด ๋งŽ์•„, ์™„์ „ํžˆ ๊ตฌํ˜„ํ•˜์ง€ ๋ชปํ–ˆ๋‹ค. ํ•„ํ„ฐ๋ง ๋กœ์ง์„ ๋งŽ์ด ๊ตฌํ˜„ํ•˜๋‹ค ์‹œ๊ฐ„์ด ์—†์–ด ๊ทธ๋ž˜ํ”„์˜ ํ˜•์‹๋“ค๋„ ์‹ ๊ฒฝ ์“ฐ์ง€ ๋ชปํ–ˆ๊ณ , adList ๋ฐ์ดํ„ฐ๋กœ ์ˆ˜์ •ํ•˜๊ธฐ ๋ถ€๋ถ„๋„ ๊ตฌํ˜„ํ•˜์ง€ ๋ชปํ–ˆ๋‹ค. ์•„์ง ๋„ˆ๋ฌด ๋งŽ์ด ๋ถ€์กฑํ•˜๋‹ค๋Š” ๊ฒƒ์„ ๋งŽ์ด ๋А๋‚€๋‹ค. ์ฝ”๋”ฉ์„ ํ•˜๋ฉด์„œ ์ €๋ฒˆ์— ํ–ˆ๋˜ ์ฝ”๋“œ๋ฅผ ๋ณต์‚ฌํ•ด ์ˆ˜์ •ํ•ด์„œ ์‚ฌ์šฉํ•˜๋‹ค ๋ณด๋‹ˆ, "์ •๋ง ๋‚ด๊ฐ€ ์ด ์ฝ”๋“œ๋“ค์„ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ณ  ์žˆ๋Š” ๊ฑธ๊นŒ"๋ผ๋Š” ์ƒ๊ฐ๋„ ๋“ค๊ณ , ๋ถ€์กฑํ•จ์— ์ขŒ์ ˆํ•˜๊ธฐ๋„ ๋งŽ์ด ํ–ˆ๋‹ค.

๊ทธ๋ž˜๋„ ๋งค๋ฒˆ ๊ณผ์ œ๋ฅผ ํ•˜๋ฉด์„œ ๋‚ด๊ฐ€ ๊ฐ€์ง„ ์žฅ์ ์€, ํ”„๋กœ์ ํŠธ ๋‚ด์˜ ๋ฌธ์ œ์ ์„ ์ž˜ ์ฐพ๋Š”๋‹ค๋Š” ์ ๊ณผ ๋ฐฐ์šด ๊ฒƒ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค. ๋‚ด ํ”„๋กœ์ ํŠธ์—์„œ ๋ถ€์กฑํ•œ ์ ๋“ค์„ ๊ธฐ๋กํ•ด์„œ ์ตœ๋Œ€ํ•œ ์ฑ„์› ๊ณ , ๋ฉ˜ํ† ๋‹˜์ด ๋ง์”€ํ•ด ์ฃผ์…จ๋˜ ๋ถ€๋ถ„๋“ค์„ ๋‹ด์•„์„œ ์ฝ”๋”ฉํ•˜๋‹ค ๋ณด๋‹ˆ ์ด์ „๊ณผ๋Š” ๋‹ค๋ฅธ ์‹œ์•ผ์™€ ๋‹ค๋ฅธ ์ˆ˜์ค€์œผ๋กœ ์ฝ”๋”ฉํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ƒ๊ฐ์„ ํ•œ๋‹ค.

์ขŒ์ ˆํ•˜๊ณ  ํž˜๋“ค์—ˆ์ง€๋งŒ ๊ฑฐ๊ธฐ์„œ ๋ฉˆ์ถ”์ง€ ๋ง๊ณ  ์ด๋ฒˆ ๋ถ€์กฑํ•œ ๋ถ€๋ถ„๋“ค์„ ๋ฐ‘๊ฑฐ๋ฆ„์œผ๋กœ ๋‹ค์Œ์œผ๋กœ ๋‚˜์•„๊ฐ€์•ผ๊ฒ ๋‹ค.

@choi2021
๋งค์ผ์˜ ์‹œํ–‰์ฐฉ์˜ค๋ฅผ ๊ธฐ๋กํ•˜๋Š” ๊ฐœ๋ฐœ์ผ์ง€์ž…๋‹ˆ๋‹ค.